#include "JPEGParser.h"
#include <assert.h>
#include <GL/gl.h>
//#include <GL/glut.h>
#include <math.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string.h>
#include "FreeImage.h"

using namespace std;

freeImage* jpegParser::singleton = NULL;
FIBITMAP* GenericLoader(const char* lpszPathName, int flag = 0);

freeImage::freeImage(void) : bIsInit(false)
{
}
void freeImage::Init(void)
{
  if (bIsInit) return;
  FreeImage_Initialise();
  bIsInit = true;
}
void freeImage::Destroy(void)
{
  if (!bIsInit) return;
  FreeImage_DeInitialise();
  bIsInit = false;
}

bool freeImage::IsInit(void)
{
  return bIsInit;
}

image::image(void) : width(0), height(0), red(NULL), green(NULL), blue(NULL), grayscale(NULL)
{
  if (!jpegParser::GetSingleton()->IsInit())
    {
      cerr << "Before using image class, you should initialize JPEGParser first" << endl;
    }
  initialize();

}

image::image(uint width, uint height)
{
  this->width = width;
  this->height = height;
  initialize();
  uint nPixels = width * height;
  pxs = new color[nPixels];
  red = new BYTE[nPixels];
  green = new BYTE[nPixels];
  blue = new BYTE[nPixels];
  grayscale = new BYTE[nPixels];
}

image::image(const image& im) : width(im.width), height(im.height), pxs(NULL), red(NULL), green(NULL), blue(NULL), grayscale(NULL)
{
  if (this == &im) return;
  if (!jpegParser::GetSingleton()->IsInit())
    {
      cerr << "Befor using image class, you should initialize JPEGParser first" << endl;
    } 
 uint nPixels = width * height;
  if (!nPixels)
    {
      initialize();
    }
  else
    {
      pxs = new color[nPixels];
      red = new BYTE[nPixels];
      green = new BYTE[nPixels];
      blue = new BYTE[nPixels];
      grayscale = new BYTE[nPixels];
      copyFrom(im);
    }
}

void image::initialize(void)
{
  memset((void*)redHist, 0, sizeof(redHist));
  memset((void*)greenHist, 0, sizeof(greenHist));
  memset((void*)blueHist, 0, sizeof(blueHist));
  memset((void*)illumHist, 0, sizeof(illumHist));
  memset((void*)&redInfo, 0, sizeof(redInfo));
  memset((void*)&greenInfo, 0, sizeof(greenInfo));
  memset((void*)&blueInfo, 0, sizeof(blueInfo));
  memset((void*)&illumInfo, 0, sizeof(illumInfo));
}

void image::copyFrom(const image& im)
{
  uint nPixels = width * height;
  memcpy(this->pxs, im.pxs, nPixels * sizeof(color));
  memcpy(this->red, im.red, nPixels * sizeof(BYTE));
  memcpy(this->green, im.green, nPixels * sizeof(BYTE));
  memcpy(this->blue, im.blue, nPixels * sizeof(BYTE));
  memcpy(this->grayscale, im.grayscale, nPixels * sizeof(BYTE));
  memcpy(this->redHist, im.redHist, sizeof(this->redHist));
  memcpy(this->greenHist, im.greenHist, sizeof(this->greenHist));
  memcpy(this->blueHist, im.blueHist, sizeof(this->blueHist));
  memcpy(this->illumHist, im.illumHist, sizeof(this->illumHist));
  memcpy(&(this->redInfo), &im.redInfo, sizeof(this->redInfo));
  memcpy(&(this->greenInfo), &im.greenInfo, sizeof(this->greenInfo));
  memcpy(&(this->blueInfo), &im.blueInfo, sizeof(this->blueInfo));
  memcpy(&(this->illumInfo), &im.illumInfo, sizeof(this->illumInfo));
}


image* image::operator-(const image& im)
{
  image* result = new image();
  uint wid = MIN(this->width, im.width);
  uint ht = MIN(this->height, im.height);
  if (wid != this->width || wid != im.width || ht != this->height || ht != im.height)
    {
      cerr << "Warning! Sizes of two images don't match! One is (" << this->width << ", " << this->height << ") and the other is (" << im.width << ", " << im.height <<")" << endl;
    }
  result->width = wid;
  result->height = ht;
  uint nPixels = wid * ht;
  result->pxs = new color[nPixels];
  result->red = new BYTE[nPixels];
  result->green = new BYTE[nPixels];
  result->blue = new BYTE[nPixels];
  result->grayscale = new BYTE[nPixels];
  uint ithPixel = 0;

  for (uint ithRow = 0; ithRow < result->height; ithRow++)
    {
      for (uint ithCol = 0; ithCol < result->width; ithCol++)
	{
	  uint index4this = ithRow * this->width + ithCol;
	  uint index4comp = ithRow * im.width + ithCol;
	  result->pxs[ithPixel].r = result->red[ithPixel] = abs(static_cast<int>(this->red[index4this]) - im.red[index4comp]);
	  result->pxs[ithPixel].g = result->green[ithPixel] = abs(static_cast<int>(this->green[index4this]) - im.green[index4comp]);
	  result->pxs[ithPixel].b = result->blue[ithPixel] = abs(static_cast<int>(this->blue[index4this]) - im.blue[index4comp]);
	  result->grayscale[ithPixel] = abs(static_cast<int>(this->grayscale[index4this]) - im.grayscale[index4comp]);
	  result->pxs[ithPixel].a = 255;
	  ithPixel++;
	}
    }
  return result;
}
image& image::operator=(const image& im)
{
  if (this == &im) return *this;
  this->width = im.GetWidth();
  this->height = im.GetHeight();
  uint nPixels = width * height;

  if (pxs) delete[] pxs;
  if (red) delete[] red;
  if (green) delete[] green;
  if (blue) delete[] blue;
  if (grayscale) delete[] grayscale;

  if (!nPixels)
    {
      red = green = blue = grayscale = NULL;
      pxs = NULL;
      initialize();
    }
  else
    {
      pxs = new color[nPixels];
      red = new BYTE[nPixels];
      green = new BYTE[nPixels];
      blue = new BYTE[nPixels];
      grayscale = new BYTE[nPixels];
      copyFrom(im);
    }
  return *this;
}

bool image::ReadFromFile(string filename, bool bMask)
{
  FIBITMAP* bitmap = GenericLoader(filename.c_str());
  if (!bitmap)
    return false;
  assert(24 == FreeImage_GetBPP(bitmap));
  assert(FIT_BITMAP == FreeImage_GetImageType(bitmap));
  width = FreeImage_GetWidth(bitmap);
  height = FreeImage_GetHeight(bitmap);
  uint nPixels = width * height;
  pxs = new color[nPixels];
  red = new BYTE[nPixels];
  green = new BYTE[nPixels];
  blue = new BYTE[nPixels];
  grayscale = new BYTE[nPixels];
  BYTE* bits = (BYTE*)FreeImage_GetBits(bitmap);
  uint pitch = FreeImage_GetPitch(bitmap);
  uint index = 0;
  for (uint y = 0; y < height; y++)
    {
      BYTE *px = (BYTE*)bits;

      for (uint x = 0; x < width; x++)
	{
	  pxs[index].r = red[index] = px[FI_RGBA_RED];
	  pxs[index].g = green[index] = px[FI_RGBA_GREEN];
	  pxs[index].b = blue[index] = px[FI_RGBA_BLUE];
	  pxs[index].a = 255;
	  grayscale[index] = static_cast<uint>(0.3 * red[index] + 0.6 * green[index] + 0.1 * blue[index]);
	  index++;
	  px += 3;
	}
      bits += pitch;
    }
  return true;
}

void image::ReadFromMemory(void)
{
  if (width == 0 || height == 0)
    {
      cerr << "You should initialize the image object first before reading from memory!" << endl;
      return;
    }
  glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pxs);
}

void image::ReadFromBuffer(void* buffer, int numBytesPerElem)
{
	if (width == 0 || height == 0)
	{
		cerr << "You should initialize the image object first before reading from memory!" << endl;
		return;
	}
	memcpy(pxs, buffer, width * height * numBytesPerElem);
}
bool image::SaveToFile(string filename)
{

  FIBITMAP* bitmap = FreeImage_Allocate(width, height, 24, 0x000000FF, 0x0000FF00, 0x00FF0000);
  if (!bitmap) return false;
  BYTE* bits = (BYTE*)FreeImage_GetBits(bitmap);
  uint pitch = FreeImage_GetPitch(bitmap);
  uint index = 0;
  for (uint y = 0; y < height; y++)
    {
      BYTE *px = (BYTE*)bits;
      for (uint x = 0; x < width; x++)
	{
	  *px++ = pxs[index].b;
	  *px++ = pxs[index].g;
	  *px++ = pxs[index].r;
	  index++;
	}
      bits += pitch;
    }
  BOOL bSuccess = FreeImage_Save(FIF_JPEG, bitmap, filename.c_str(), JPEG_QUALITYSUPERB);
  FreeImage_Unload(bitmap);
  return bSuccess;
}

void image::draw(void)
{
  glDrawPixels (width, height, GL_RGBA, GL_UNSIGNED_BYTE, pxs);
}

void image::histogram(image* mask)
{
  uint numPixels = width * height;
  calculateHistogram(red, numPixels, redHist, mask);  
  calculateHistogram(green, numPixels, greenHist, mask);  
  calculateHistogram(blue, numPixels, blueHist, mask);  
  calculateHistogram(grayscale, numPixels, illumHist, mask);  
}

void image::statistics(image* mask)
{
  uint numPixels = width * height;
  calculateStatisticalInfo(red, numPixels, &redInfo, mask);
  calculateStatisticalInfo(green, numPixels, &greenInfo, mask);
  calculateStatisticalInfo(blue, numPixels, &blueInfo, mask);
  calculateStatisticalInfo(grayscale, numPixels, &illumInfo, mask);
}

uint* image::GetHistogram(channel c)
{
  switch (c)
    {
    case R: return redHist;
    case G: return greenHist;
    case B: return blueHist;
    case L: return illumHist;
    default: return NULL;
    }
}

uint image::GetMean(channel c)
{
  switch (c)
    {
    case R: return redInfo.mean;
    case G: return greenInfo.mean;
    case B: return blueInfo.mean;
    case L: return illumInfo.mean;
    default: return 0;
    }
}

float image::GetVariance(channel c)
{
  switch (c)
    {
    case R: return redInfo.variance;
    case G: return greenInfo.variance;
    case B: return blueInfo.variance;
    case L: return illumInfo.variance;
    default: return 0;
    }
}

float image::GetError(channel c)
{
  switch (c)
    {
    case R: return redInfo.error;
    case G: return greenInfo.error;
    case B: return blueInfo.error;
    case L: return illumInfo.error;
    default: return 0;
    }
}

image::~image(void)
{
  if (pxs)
    delete[] pxs;
  if (red)
    delete[] red;
  if (blue)
    delete[] blue;
  if (green)
    delete[] green;
  grayscale = red = blue = green = NULL;
  pxs = NULL;
  width = height = 0;
}

void image::calculateHistogram(BYTE* channel, uint numPixels, uint* histogram, image* mask)
{
  if (!mask)
    {
      for (uint ithPixel = 0; ithPixel < numPixels; ithPixel++)
	{
	  ++histogram[channel[ithPixel]];
	}
    }
  else
    {
      uint wid = MIN(this->width, mask->width);
      uint ht = MIN(this->height, mask->height);
      uint numEffectPixels = 0;
      for (uint rows = 0; rows < ht; rows++)
	{
	  for (uint cols = 0; cols < wid; cols++)
	    {
	      uint index4this = rows * this->width + cols;
	      uint index4mask = rows * mask->width + cols;
	      if (mask->red[index4mask] > NUM_COLOR / 2)
		{
		  ++histogram[channel[index4this]];
		  numEffectPixels++;
		}
	    }
	}
    }
}
void image::dumpHistogram(string filename)
{
	string realFileName = filename + "R";
	ofstream outFileR(realFileName.c_str());
	if (!outFileR)
	{
		cerr << "Cannot open output file \"" << realFileName << "\"" << endl;
		return;

	}
	for (uint ithColor = 0; ithColor < NUM_COLOR; ithColor++)
	{
		outFileR << setw(7) << ithColor << "    " << redHist[ithColor] << endl;
	}

	realFileName = filename + "G";
	ofstream outFileG(realFileName.c_str());
	if (!outFileG)
	{
		cerr << "Cannot open output file \"" << realFileName << "\"" << endl;
		return;
	}
	for (uint ithColor = 0; ithColor < NUM_COLOR; ithColor++)
	{
		outFileG << setw(7) << ithColor << "    " << greenHist[ithColor] << endl;
	}
	
	realFileName = filename + "B";
	ofstream outFileB(realFileName.c_str());
	if (!outFileB)
	{
		cerr << "Cannot open output file \"" << realFileName << "\"" << endl;
		return;
	}
	for (uint ithColor = 0; ithColor < NUM_COLOR; ithColor++)
	{
		outFileB << setw(7) << ithColor << "    " << blueHist[ithColor] << endl;
	}

	realFileName = filename + "L";
	ofstream outFileL(realFileName.c_str());
	if (!outFileL)
	{
		cerr << "Cannot open output file \"" << realFileName << "\"" << endl;
		return;
	}
	for (uint ithColor = 0; ithColor < NUM_COLOR; ithColor++)
	{
		outFileL << setw(7) << ithColor << "    " << illumHist[ithColor] << endl;
	}
}

void image::calculateStatisticalInfo(BYTE* channel, uint numPixels, statisticalInfo* info, image* mask)
{
  uint total = 0;
  double total4variance = 0.f;
  if (!mask)
    {
      for (uint ithPixel = 0; ithPixel < numPixels; ithPixel++)
	{
	  total += channel[ithPixel];
	}
      info->mean = static_cast<uint>(static_cast<double>(total) / numPixels + 0.5);
      for (uint ithPixel = 0; ithPixel < numPixels; ithPixel++)
	{
	  total4variance += (channel[ithPixel] - info->mean) * (channel[ithPixel] - info->mean);
	}
      info->variance = sqrt(total4variance / numPixels);
    }
  else
    {
      uint wid = MIN(this->width, mask->width);
      uint ht = MIN(this->height, mask->height);
      uint numEffectPixels = 0;
      for (uint rows = 0; rows < ht; rows++)
	{
	  for (uint cols = 0; cols < wid; cols++)
	    {
	      uint index4this = rows * this->width + cols;
	      uint index4mask = rows * mask->width + cols;
	      if (mask->red[index4mask] > NUM_COLOR / 2)
		{
		  total += channel[index4this];
		  numEffectPixels++;
		}
	    }
	}
      info->mean = total / numEffectPixels;
      for (uint rows = 0; rows < ht; rows++)
	{
	  for (uint cols = 0; cols < wid; cols++)
	    {
	      uint index4this = rows * this->width + cols;
	      uint index4mask = rows * mask->width + cols;
	      if (mask->red[index4mask] > NUM_COLOR / 2)
		{
		  total4variance += (channel[index4this] - info->mean) * (channel[index4this] - info->mean);
		}
	    }
	}
        info->variance = sqrt(total4variance / numEffectPixels);
    }
}

void image::calculateError(BYTE* channel, uint numPixels, statisticalInfo* info, image* mask)
{
  double total = 0;
  if (!mask)
    {   
      for (uint ithPixel = 0; ithPixel < numPixels; ithPixel++)
	{
	  total += channel[ithPixel] * channel[ithPixel];
	}
      info->error = sqrt(total) / (numPixels * NUM_COLOR);
    }
  else
    {
      uint wid = MIN(this->width, mask->width);
      uint ht = MIN(this->height, mask->height);
      uint numEffectPixels = 0;
      for (uint rows = 0; rows < ht; rows++)
	{
	  for (uint cols = 0; cols < wid; cols++)
	    {
	      uint index4this = rows * this->width + cols;
	      uint index4mask = rows * mask->width + cols;
	      if (mask->red[index4mask] > NUM_COLOR / 2)
		{
		  total += channel[index4this] * channel[index4this];
		  numEffectPixels++;
		}
	    }
	}
        info->error = sqrt(total);
    }
}

void image::errorTo(image* comparisonImage, image* mask) //comparisonImage is the realPhoto
{
  image* differenceImage = *this - (*comparisonImage);
  uint nPixels = differenceImage->width * differenceImage->height;
  calculateError(differenceImage->red, nPixels, &redInfo, mask);
  calculateError(differenceImage->green, nPixels, &greenInfo, mask);
  calculateError(differenceImage->blue, nPixels, &blueInfo, mask);
  calculateError(differenceImage->grayscale, nPixels, &illumInfo, mask);
  uint comparisonImageArea = 0;
  if (!mask)
    comparisonImageArea = comparisonImage->GetWidth() * comparisonImage->GetHeight();
  else
    {
      for (uint rows = 0; rows < mask->height; rows++)
	{
	  for (uint cols = 0; cols < mask->width; cols++)
	    {
	      uint index4mask = cols + rows * mask->width;
	      if (mask->red[index4mask] > NUM_COLOR / 2)
		{
		  comparisonImageArea++;
		}
	    }
	}
    }
  redInfo.error /= comparisonImageArea * comparisonImage->redInfo.mean;
  greenInfo.error /= comparisonImageArea * comparisonImage->greenInfo.mean;
  blueInfo.error /= comparisonImageArea * comparisonImage->blueInfo.mean;
  illumInfo.error /= comparisonImageArea * comparisonImage->illumInfo.mean;
}

FIBITMAP* GenericLoader(const char* lpszPathName, int flag) 
{
  FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
  fif = FreeImage_GetFileType(lpszPathName, 0);
  if(fif == FIF_UNKNOWN) {
    fif = FreeImage_GetFIFFromFilename(lpszPathName);
  }
  if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
    FIBITMAP *dib = FreeImage_Load(fif, lpszPathName, flag);
    return dib;
  }
  return NULL;
}

